package konggao.util.bean;

import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;

import org.apache.commons.beanutils.BeanUtilsBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 通过Properties配置文件,构造指定类型的对象,并set属性
 * 
 * @format 
 *         https://code.google.com/p/google-styleguide/source/browse/trunk/eclipse-java-google-style.
 *         xml
 * @author whua
 * @email smtp2006@126.com
 * @version 2014年3月29日下午11:26:55
 */
public class PropertiesBeanUtil {
  private static final Logger log = LoggerFactory.getLogger(PropertiesBeanUtil.class);
  /**
   * commons-lang的BeanUtilsBean代理
   */
  public BeanUtilsBean proxy;

  /**
   * 默认情况下用BeanUtilsBean.instance;
   */
  public PropertiesBeanUtil() {
    super();
    this.proxy = BeanUtilsBean.getInstance();
  }

  /**
   * 自定义BeanUtilsBean,可以注册自定义的类型Converter
   * 
   * @param proxy
   */
  public PropertiesBeanUtil(BeanUtilsBean proxy) {
    super();
    this.proxy = proxy;
  }

  /**
   * 指定Properties文件
   * 
   * @param beanClass 返回Object的类型
   * @param props 配置文件
   * @return
   * @throws Exception
   */
  @SuppressWarnings("rawtypes")
  public Object getBean(Class beanClass, Properties props) throws Exception {
    Object bean = beanClass.newInstance();

    populate(bean, props);
    return bean;
  }

  /**
   * 指定文件路径
   * 
   * @param beanClass 返回Object的类型
   * @param proFile 配置文件路径
   * @return
   * @throws Exception
   */
  @SuppressWarnings("rawtypes")
  public Object getBean(Class beanClass, String proFile) throws Exception {
    InputStream inStream = PropertiesBeanUtil.class.getClassLoader().getResourceAsStream(proFile);
    Properties props = new Properties();
    props.load(inStream);
    return getBean(beanClass, props);
  }

  /**
   * 默认采用同Class路径的.properties配置文件模式
   * 
   * @param beanClass 返回Object的类型
   * @return
   * @throws Exception
   */
  @SuppressWarnings("rawtypes")
  public Object getBean(Class beanClass) throws Exception {
    String proFile = beanClass.getName() + ".properties";
    InputStream inStream = PropertiesBeanUtil.class.getClassLoader().getResourceAsStream(proFile);
    Properties props = new Properties();
    props.load(inStream);
    return getBean(beanClass, props);
  }

  /**
   * 这里对BeanUtilsBean的populate做了扩展,BeanUtilsBean的嵌套不通用
   * 
   * <pre>
   * 把properties的<Key,Value>set到指定bean对应的属性
   * </pre>
   * 
   * @param bean
   * @param properties
   * @throws IllegalAccessException
   * @throws InvocationTargetException
   * @throws Exception
   */
  @SuppressWarnings("rawtypes")
  private void populate(Object bean, Map properties) throws IllegalAccessException,
      InvocationTargetException, Exception {

    // Do nothing unless both arguments have been specified
    if ((bean == null) || (properties == null)) {
      return;
    }
    // Loop through the property name/value pairs to be set
    Iterator entries = properties.entrySet().iterator();
    while (entries.hasNext()) {

      // Identify the property name and value(s) to be assigned
      Map.Entry entry = (Map.Entry) entries.next();
      String name = (String) entry.getKey();
      if (name == null) {
        continue;
      }
      setProperty(bean, name, entry.getValue(), null, properties);
    }

  }

  /**
   * bean.set[name] = value
   * 
   * <pre>
   * parentName在嵌套的属性为抽象类时,需要指定对应属性的class来指定实现类初始化,这个时候需要全局配置
   * </pre>
   * 
   * @param bean
   * @param name
   * @param value
   * @param parentName 递归嵌套参数
   * @param properties
   * @throws Exception
   */
  @SuppressWarnings("rawtypes")
  private void setProperty(Object bean, String name, Object value, String parentName, Map properties)
      throws Exception {
    if (log.isDebugEnabled()) {
      log.debug("setProperty({}, {},  {})", bean.getClass().getName(), name, value);
    }
    // e.g:index.name
    if (isNestedProperty(name)) {
      if (log.isDebugEnabled()) {
        log.debug("isNestedProperty({})", name);
      }
      // Perform the assignment for this property, e.g:getNestedName( index.name ) = index
      NestedProperty nestedProperty = getNestedProperty(name);
      Object nestedBean = getProperty(bean, nestedProperty.getBean());
      String mapEntryKey = (parentName == null ? "" : parentName) + '.' + nestedProperty.getBean();
      if (nestedBean == null) {
        if (log.isDebugEnabled()) {
          log.debug("nestedBean({}) is null, create it", nestedProperty.getBean());
        }
        // if index bean is null, create and set
        Class propertyType =
            proxy.getPropertyUtils().getPropertyDescriptor(bean, nestedProperty.getBean())
                .getPropertyType();

        nestedBean = newInstance(mapEntryKey, propertyType, properties);
        proxy.setProperty(bean, nestedProperty.getBean(), nestedBean);
      }
      setProperty(nestedBean, nestedProperty.getProperty(), value, mapEntryKey, properties);
    } else {
      proxy.setProperty(bean, name, value);
    }

  }

  @SuppressWarnings("rawtypes")
  private Object newInstance(String property, Class propertyType, Map properties) throws Exception {
    Object value = null;
    if (Modifier.isAbstract(propertyType.getModifiers())) {
      Object targetClass = properties.get(property + ".class");
      if (targetClass == null) {
        throw new RuntimeException(property + ".class property required");
      }
      value = Class.forName(targetClass.toString()).newInstance();
    } else {
      value = propertyType.newInstance();
    }
    return value;
  }

  /**
   * 是否是嵌套的属性
   * 
   * <pre>
   * 例如:Car.tire.name=风神,isNestedProperty(tire.name)=true
   * </pre>
   * 
   * @param name
   * @return
   */
  private boolean isNestedProperty(String name) {
    return name.indexOf('.') != -1;
  }

  /**
   * car.tire.name=NestedProperty(car,tire.name)
   * 
   * @param name
   * @return
   */
  private NestedProperty getNestedProperty(String name) {
    int nextedIndex = name.indexOf(".");
    NestedProperty np =
        new NestedProperty(name.substring(0, nextedIndex), name.substring(nextedIndex + 1));

    return np;
  }

  /**
   * BeanUtilsBean.getProperty的返回结果前用Converter.convert,把结果转为String
   * 
   * <pre>
   * 这里不做convert,这样在嵌套的时候才能返回对象
   * </pre>
   * 
   * @param bean
   * @param name
   * @return
   * @throws IllegalAccessException
   * @throws InvocationTargetException
   * @throws NoSuchMethodException
   */
  private Object getProperty(Object bean, String name) throws IllegalAccessException,
      InvocationTargetException, NoSuchMethodException {

    Object value = proxy.getPropertyUtils().getNestedProperty(bean, name);
    return value;

  }

  /**
   * <pre>
   * car.tire.name,多级属性时,区分一级属性和嵌套子属性
   * </pre>
   * 
   * @format 
   *         https://code.google.com/p/google-styleguide/source/browse/trunk/eclipse-java-google-style
   *         .xml
   * @author whua
   * @email smtp2006@126.com
   * @version 2014年3月29日下午11:52:00
   */
  private static class NestedProperty {
    /**
     * 一级属性
     */
    private String bean;
    /**
     * 嵌套子属性
     */
    private String property;

    /**
     * @param bean
     * @param property
     */
    public NestedProperty(String bean, String property) {
      super();
      this.bean = bean;
      this.property = property;
    }

    public String getBean() {
      return bean;
    }

    public String getProperty() {
      return property;
    }

  }
}
